#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <assert.h>
#include <sys/wait.h>

#define MAX_PATH 255
#define ONE_M (1024*1024)
#define LINE_SIZE 1024

int copy(const char *src, const char *dst, int processNum);
int multi_process_copy(const char *src, const char *dst, int processNum);
int copy_process(const char *src, const char *dst, int paceSize, int processNum, int srcFileSize);
int core_copy(int fdSrc, int fdDst, int posBegin, int posEnd, int srcFileSize, int flag);
const char *getRetValue(int retCode);

enum error_no
{
	ERROR_SUCCESS = 0,
	ERROR_INVALID_PARAMS = 1,
	ERROR_INVALID_PERMITS = 2,
	ERROR_INVALID_FILE_STAT = 3,
	ERROR_INVALID_DIR = 4,
	ERROR_FILE_CREATE = 5,
	ERROR_COPY_FAIL = 6,
	ERROR_OPEN_FILE = 7,
	ERROR_WAIT_CHILD_PROCESS = 8,
	ERROR_SEEK_FILE = 9,
	ERROR_WRITE_ERROR = 10
};

int main(int argc, char *argv[])
{
	if (argc != 4)
	{
		printf("usage: %s src dst process_num\n", argv[0]);
		return ERROR_INVALID_PARAMS;
	}
	
	char src[MAX_PATH] = {0};
	char dst[MAX_PATH] = {0};
	int processNum = 0;
	
	strncpy(src, argv[1], strlen(argv[1]));
	strncpy(dst, argv[2], strlen(argv[2]));
	processNum = (int)atoi(argv[3]);
	
	if (processNum < 0) //auto
	{
		processNum = 0;
	}
	
	int ret = copy(src, dst, processNum);
	printf("result: %s\n", getRetValue(ret));
}

int copy(const char *src, const char *dst, int processNum)
{
	assert(src != NULL);
	assert(dst != NULL);
	
	if (access(src, F_OK) < 0)
	{
		perror("access");
		return ERROR_INVALID_PERMITS;
	}
	
	struct stat fileInfo;
	
	if (stat(src, &fileInfo) < 0)
	{
		perror("stat");
		return ERROR_INVALID_FILE_STAT;
	}
	
	if (fileInfo.st_mode & S_IFDIR)
	{
		return ERROR_INVALID_DIR;
	}
	
	if (access(dst, F_OK) < 0)
	{
		int fd = open(dst, O_CREAT, 0755);
		if (!fd)
		{
			perror("create");
			return ERROR_FILE_CREATE;
		}
		close(fd);
	}
	else
	{
		int fd = open(dst, O_TRUNC);
		if (!fd)
		{
			perror("open");
			return ERROR_FILE_CREATE;
		}
		close(fd);
	}
	
	return multi_process_copy(src, dst, processNum);
}

int multi_process_copy(const char *src, const char *dst, int processNum)
{
	assert(src != NULL);
	assert(dst != NULL);
	
	struct stat fileInfo;
	if (stat(src, &fileInfo) < 0)
	{
		perror("stat");
		return ERROR_INVALID_FILE_STAT;
	}
	
	int srcFileSize = (int) fileInfo.st_size;
	int paceSize = 0;
	
	if (processNum == 0)
	{
		processNum = srcFileSize / ONE_M + 1;
		paceSize = ONE_M;
	}
	else
	{
		if (processNum > 1)
		{
			paceSize = srcFileSize / processNum + srcFileSize % processNum;
		}
		else
		{
			paceSize = srcFileSize;
		}
	}
	
	int retCode = 0;
	if ( ( retCode = copy_process(src, dst, paceSize, processNum, srcFileSize)) == ERROR_SUCCESS )
	{
		return ERROR_SUCCESS;
	}
	else
	{
		return ERROR_COPY_FAIL;
	}
}

int copy_process(const char *src, const char *dst, int paceSize, int processNum, int srcFileSize)
{
	assert(src != NULL);
	assert(dst != NULL);
	
	if (paceSize == 0)
	{
		return ERROR_SUCCESS;
	}
	
	if (srcFileSize > 0)
	{
		int fdDst = open(dst, O_WRONLY);
		if (!fdDst)
		{
			perror("open");
			return ERROR_OPEN_FILE;
		}
		
		lseek(fdDst, srcFileSize, SEEK_SET);
		write(fdDst, '\0', 1);
	}
	
	
	pid_t pid;
	int i = 0;
	for (; i < processNum; i++)
	{
		pid = fork();
		if (pid == 0)
		{
			break;
		}
	}
	
	if (pid == 0)
	{
		int fdSrc = open(src, O_RDONLY);
		if (!fdSrc)
		{
			perror("open");
			return ERROR_OPEN_FILE;
		}
		
		int fdDst = open(dst, O_WRONLY);
		if (!fdDst)
		{
			perror("open");
			return ERROR_OPEN_FILE;
		}
		
		int posBegin = i * paceSize;
		int posEnd = posBegin + paceSize;
		
		//printf("process[%d](%d--%d), total: %d\n", i, posBegin, posEnd, posEnd - posBegin);
		if ( posBegin <= srcFileSize && posEnd > srcFileSize )
		{
			return core_copy(fdSrc, fdDst, posBegin, posEnd, srcFileSize, 1);
		}
		else if ( posBegin <= srcFileSize && posEnd <= srcFileSize )
		{
			return core_copy(fdSrc, fdDst, posBegin, posEnd, srcFileSize, 0);
		}
		else
		{
			return ERROR_SUCCESS;
		}
	}
	else
	{
		int status;
		while(waitpid(-1, &status, 0) > 0)
		{
			if ( WIFEXITED(status) == 0 )
			{
				unlink(dst);
				return ERROR_COPY_FAIL;
			}
		}
	}
	
	return ERROR_SUCCESS;
}

int core_copy(int fdSrc, int fdDst, int posBegin, int posEnd, int srcFileSize, int flag)
{
	off_t size = 0;
		
	if (lseek(fdSrc, posBegin, SEEK_SET) < 0)
	{
		return ERROR_SEEK_FILE;
	}
	
	if (lseek(fdDst, posBegin, SEEK_SET) < 0)
	{
		return ERROR_SEEK_FILE;
	}
	
	int length = 0;
	if (flag)
	{
		length = srcFileSize - posBegin;
	}
	else
	{
		length = posEnd - posBegin;
	}
	
	char line[LINE_SIZE] = {0};
	do
	{
		if ( length >= LINE_SIZE )
		{
			//printf("1.length: %d\n", length);
			memset(line, 0, sizeof(line));
			if ((size = read(fdSrc, line, sizeof(line)))> 0)
			{
				if ( write(fdDst, line, size) != size )
				{
					return ERROR_WRITE_ERROR;
				}
			}
			length -= LINE_SIZE;
		}
		else
		{
			//printf("2.length: %d\n", length);
			memset(line, 0, sizeof(line));
			if ((size = read(fdSrc, line, length))> 0)
			{
				if ( write(fdDst, line, size) != size )
				{
					return ERROR_WRITE_ERROR;
				}
				break;
			}
			break;
		}
	}while(1);
	
	return ERROR_SUCCESS;
}

const char *getRetValue(int retCode)
{
	switch(retCode)
	{
		case ERROR_SUCCESS:
			return "copy complete!!!";
			break;
		case ERROR_INVALID_PARAMS:
			return "invalid params";
			break;
		case ERROR_INVALID_FILE_STAT:
			return "cannot stat the file";
			break;
		case ERROR_INVALID_DIR:
			return "the src file cannot be directory";
			break;
		case ERROR_FILE_CREATE:
			return "error in creating the dest file";
			break;
		case ERROR_COPY_FAIL:
			return "copy file error";
			break;
		case ERROR_OPEN_FILE:
			return "open file error";
			break;
		case ERROR_WAIT_CHILD_PROCESS:
			return "cannot get the child status";
			break;
		case ERROR_SEEK_FILE:
			return "cannot seek this file, maybe not regular file";
			break;
		case ERROR_WRITE_ERROR:
			return "cannot write the dst file";
			break;
	}
}

/*
实现文件多进程拷贝。
假设有一个超大文件，需对其完成拷贝工作。为提高效率，可采用多进程并行拷贝的方法来实现。假设文件大小为len，共有n个进程对该文件进行拷贝。那每个进程拷贝的字节数应为len/n。但未必一定能整除，我们可以选择让最后一个进程负责剩余部分拷贝工作。可使用len % (len/n)将剩余部分大小求出。
为降低实现复杂度，可选用mmap来实现源、目标文件的映射，通过指针操作内存地址，设置每个进程拷贝的起始、结束位置。借助MAP_SHARED选项将内存中所做的修改反映到物理磁盘上。
*/
